package cM

import (
	"bytes"
	"database/sql"
	"db2s/outPut"
	"fmt"
	"os"
	"reflect"
	"strings"
)

var (
	mode1 string
)

type DSNsS struct {
	SrcDSN                 string `json:"srcDSN"`
	DstDSN                 string `json:"dstDSN"`
	SrcDBName              string `json:"src_dbname"`
	DestDBName             string `json:"dst_dbname"`
	SrcDrive               string `json:"src_drive"`
	SrcJdbc                string `json:"src_jdbc"`
	DestDrive              string `json:"dst_drive"`
	DestJdbc               string `json:"dst_jdbc"`
	CheckParameterSourceDB *sql.DB
	CheckParameterDestDB   *sql.DB
}

func singleDSns(dSns DSNsS) (s string) {
	if strings.EqualFold(mode1, "load-output") {
		return fmt.Sprintf("DSNs{\n\tsrcDSN = \"%v|%v\"\n}", dSns.SrcDBName, dSns.SrcJdbc)
	}
	if strings.EqualFold(mode1, "load-input") {
		return fmt.Sprintf("DSNs{\n\tsrcDSN = \"%v|%v\"\n}", dSns.DestDBName, dSns.DestJdbc)
	}
	return fmt.Sprintf("DSNs{\n\tsrcDSN = \"%v|%v\"\n}", dSns.SrcDBName, dSns.SrcJdbc)
}
func twoDSns(dSns DSNsS) string {
	return fmt.Sprintf("DSNs{\n\tsrcDSN = \"%v|%v\"\n\tdstDSN = \"%v|%v\"\n}", dSns.SrcDBName, dSns.SrcJdbc, dSns.DestDBName, dSns.DestJdbc)
}
func singleDsnType(mode string) bool {
	var singleDsnGenerate = map[string]int{
		"load-output": 1,
		"load-input":  1,
	}
	if _, ok := singleDsnGenerate[mode]; ok {
		return true
	}
	return false
}
func DSnsConfig(dSns DSNsS, mode string) string {
	if singleDsnType(mode) {
		return singleDSns(dSns)
	}
	if mode == "init" {
		return dSnsExample()
	}
	return twoDSns(dSns)
}
func HeadConfig(dSns DSNsS, mode string) string {
	var head = `// db2s configuration file reference
// Define source and target data sources
// Currently only supports MySQL and Oracle two data sources
`
	return fmt.Sprintf("%v\n%v", head, DSnsConfig(dSns, mode))
}
func generalTableRowsTaskGroup() (res []string) {
	for _, vv := range []string{"object/table"} {
		for _, l1 := range []string{"10w", "100w", "1000w", "1y", "10y", "100y"} {
			for _, l2 := range []string{"MissIndex", "Primary", "Unique", "Index"} {
				for _, l3 := range []string{"Lob", "Text", "Normal"} {
					res = append(res, fmt.Sprintf("%v/%s_%s_%s", vv, l2, l1, l3))
				}
			}
		}
	}
	return
}
func generalTableMetaTaskGroup() (res []string) {
	for _, v := range []string{"Alter", "Create"} {
		res = append(res, v)
	}
	return
}
func fullRowsTableObjectHead(subConfigFileName []outPut.FileOperations, objectFile any) (res []string) {
	var taskSeq int
	for _, v := range generalTableRowsTaskGroup() {
		var exec bool
		for _, sub := range subConfigFileName {
			if strings.HasSuffix(fmt.Sprintf("%v", sub.Print(outPut.FileName)), v) {
				exec = true
				break
			}
		}
		if !exec {
			continue
		}
		switch objectFile.(type) {
		case outPut.FileOperations:
			taskSeq++
			res = append(res, fmt.Sprintf("%v :{include \"%v\"}", taskSeq, v))
		case map[string]outPut.FileOperations:
			if _, ok := objectFile.(map[string]outPut.FileOperations)[v]; ok {
				taskSeq++
				res = append(res, fmt.Sprintf("%v :{include \"%v\"}", taskSeq, v))
			}
		case map[string]*os.File:
			if _, ok := objectFile.(map[string]*os.File)[v]; ok {
				taskSeq++
				res = append(res, fmt.Sprintf("%v :{include \"%v\"}", taskSeq, v))
			}
		}
	}
	return
}
func metaObjectHead(subConfigFileName []outPut.FileOperations, mode string) (res []string) {
	var taskSeq int
	for _, v := range generalTableMetaTaskGroup() {
		var exec bool
		var objectFile string
		for _, sub := range subConfigFileName {
			if strings.HasSuffix(fmt.Sprintf("%v", sub.Print(outPut.FileName)), v) {
				exec = true
				strRes := strings.SplitN(fmt.Sprintf("%v", sub.Print(outPut.FileName)), mode+"/", 2)
				if len(strRes) > 1 {
					objectFile = strRes[1]
				}
				break
			}
		}
		if !exec {
			continue
		}
		taskSeq++
		res = append(res, fmt.Sprintf("%v :{include \"%v\"}", taskSeq, objectFile))
	}
	return
}
func schemaConfigTableHead(subConfigFileName []outPut.FileOperations, objectFile any, mode string) (res []string) {
	switch mode {
	case "sync", "rows":
		return fullRowsTableObjectHead(subConfigFileName, objectFile)
	case "struct", "object":
		return metaObjectHead(subConfigFileName, mode)
	}
	return
}
func schemaConfigTableJoinHead(mode string) (res string) {
	switch mode {
	case "sync":
		res = fmt.Sprintf("objectJoin = %v\n", "join")
	case "struct", "object":
		res = fmt.Sprintf("objectJoin = %v\n", "left")
	case "init":
		return ""
	default:
		res = fmt.Sprintf("objectJoin = %v\n", "join")
	}
	return
}
func schemaConfig(subConfigFileName []outPut.FileOperations, objectFile any, mode string) string {
	return fmt.Sprintf("schema {\n"+
		"\t/*\n\t\tSet object objects\n\t*/\n"+
		"\tobject = {\n"+
		"\t\t//Note: In the previous object, 1, 2, 3, 4, 5, and 6 are the order numbers for tasks. By default, the smallest and highest priority are used.\n"+
		"\t\t%v\n\t}\n"+
		"\t/*\n\t\tSet ignored object objects\n\t*/\n\tignore = {schema3 = [\"object3\"]}\n"+
		//"\t/*\n\t\tSelect the object to be verified. optional value[full]\n\t\t//miss,index,\n\t*/\n\tobject = %v \n"+
		"\t/*\n\t\tSet the merging mode of object objects, optional value[left,join,right]\n\t*/\n\t %v\n"+
		"}\n",
		strings.Join(schemaConfigTableHead(subConfigFileName, objectFile, mode), "\n\t\t"),
		schemaConfigTableJoinHead(mode))
}
func structMode() string {
	var s1 = `
	struct {
		object = [column,index,partition]
        /*
          check table struct dataType
        */
		plan = {column:[name,type,null,default,comment,chart,collate],index:[]}
		/*
			alter statement repair method, optional values [single,many]
            [single] alter table split by columns. For example:
				alter table A add column a1 int;
                alter table A add column a2 int;
            [many] alter table split by table
				alter table A add column a1 int,add column a2 int;
		*/
        alterRule = single
		/*
			Whether to enable task resumption at breakpoints. After enabling it, successfully migrated tables will be automatically skipped.
			[true] -----> Skip the contents of the file result/breakPointFinish.point		
		*/
		breakpoint=false
		/*
			When enabled, existing objects will be cleared on the target end
			[true] -----> Execute the command drop [object] on the other end
		*/
		drop = false
        /*
            Whether the session connection retains the current sql_mode when querying and writing the original target database, optional value[true,false]
            [true] ---> set sql_mode=''
        */
        sqlMode=false
		/*
			Whether to ignore session-level sql_log_binlog writes when writing to the data migration target, optional value[true,false]
			[true] -----> set session sql_log_bin=0 ignores binlog writing of dml statements.
			Only effective for MySQL system database
		*/
		logBin= false
		/*
		    Set whether to optimize write performance parameters on the target side.Enabling it will adjust some parameters on the target side.
		    [true]  ----> Enable writing tuning parameters
		    Only effective for MySQL system database
		*/
		paramOpt = false
	}`
	return s1
}
func objectMode() string {
	var s1 = `
	object {
        /*
          check table partition dataType. optional value[view,job,procedure,func,trigger,package,foreign,seq]
        */
        object = [view,job,procedure,func,trigger,package,foreign,seq,type]
		/*
			alter statement repair method, optional values [single,many]
            [single] alter table split by columns. For example:
				alter table A add column a1 int;
                alter table A add column a2 int;
            [many] alter table split by table
				alter table A add column a1 int,add column a2 int;
		*/
        alterRule = single
		/*
			Whether to enable task resumption at breakpoints. After enabling it, successfully migrated tables will be automatically skipped.
			[true] -----> Skip the contents of the file result/breakPointFinish.point		
		*/
		breakpoint=false
		/*
			When enabled, existing objects will be cleared on the target end
			[true] -----> Execute the command drop [object] on the other end
		*/
		drop = false
        /*
            Whether the session connection retains the current sql_mode when querying and writing the original target database, optional value[true,false]
            [false] ---> set sql_mode=''
        */
        sqlMode=false
		/*
			Whether to ignore session-level sql_log_binlog writes when writing to the data migration target, optional value[true,false]
			[true] -----> set session sql_log_bin=0 ignores binlog writing of dml statements.
			Only effective for MySQL system database
		*/
		logBin= false
		/*
		    Set whether to optimize write performance parameters on the target side.Enabling it will adjust some parameters on the target side.
		    [true]  ----> Enable writing tuning parameters
		    Only effective for MySQL system database
		*/
		paramOpt = false
	}`
	return s1
}
func syncMode() string {
	var s1 = `sync {
		/*
			Whether the target side should truncate table during data migration, optional value[true,false]
		*/
		truncate = false
		/*
			Whether to ignore if the target table has more columns than the source table during data migration, optional value[true,false]
		*/
		igColumn = false
		/*
			Whether the session connection retains the current sql_mode when querying and writing the original target database, optional value[true,false]
			[false] ---> set sql_mode=''
		*/
		sqlMode=false
		/*
			Whether to enable task resumption at breakpoints. After enabling it, successfully migrated tables will be automatically skipped.
			[true] -----> Skip the contents of the file result/breakPointFinish.point	
		*/
		breakpoint=false
		/*
			Whether to ignore session-level sql_log_binlog writes when writing to the data migration target, optional value[true,false]
			[true] -----> set session sql_log_bin=0 ignores binlog writing of dml statements.
			Only effective for MySQL system database
		*/
		logBin= false
		/*
			Set the consistency snapshot point. If it is 0, it means not to enable it. To enable it, it must be greater than 0.
			Limitation: 
				1) scn can only query the source side of the scope.
				2) Currently, scn is only effective for Oracle, so stay tuned for MySQL.
			get scn --> SELECT CURRENT_SCN FROM V$DATABASE;
		*/
		scn=0
		/*
		    Set whether to optimize write performance parameters on the target side.Enabling it will adjust some parameters on the target side.
		    [true]  ----> Enable writing tuning parameters
		    Only effective for MySQL system database
		*/
		paramOpt = false
		/*
			Set whether to perform analyze table behavior on the target side. Optional value[true,false]
			[true]  ----> The target side executes analyze table
			[false]  -->  The target does not execute analyze table
		*/
		analyze = true
		/*
			Whether to enable the recycle bin. Once enabled, the migration objects will be backed up and retained in the gtRecover database.Optional value[true,false]
			[true]  ----> The peer executes rename table to gtrecover.schema@table_2023040506070809
			[false]  ---> No recovery backup
		*/
		recover = false
		/*
            Set transaction execution timeout to prevent lock waiting for too long (Unit is seconds).
            If it is 0, it means not to enable it. To enable it, it must be greater than 0.
		*/
		timeOut = 3600
		/*
			Set the specified number of rows for table data migration
			[0] If it is 0 means disable, full data migration
			[>0] Greater than 0 indicates the number of rows to be migrated.
			For example:
				rowsLimit = 10  ==> Each migration object migrates 10 rows of data
		*/
		rowsLimit = 0
	}`
	return s1
}
func loadMode() string {
	var (
		s1 = `load {
        /*
			Whether the target side should truncate table during data migration, optional value[true,false]
		*/
        truncate = false
        /*
			Whether to ignore if the target table has more columns than the source table during data migration, optional value[true,false]
		*/
        igColumn = false
        /*
			Whether the session connection retains the current sql_mode when querying and writing the original target database, optional value[true,false]
			[false] ---> set sql_mode=''
		*/
        sqlMode=false
        /*
			How to import and export load data. Optional value[input,output]
		*/
        mode=`
		s2 = `
       /*
			Load imports and exports the specified path or file. When specifying the file name, the file must exist.	
       */
       filePath="./dataFix"
       /*
			Whether the load output file is compressed? If output is selected as true, input must also be true.
			Note: This function is not used yet
		*/
       gzip=false
       /*
			Whether the load output file is encrypted. When output is true, input must also be true.
			Note: This function is not used yet
       */
       safe=false
	}`
		modeConfig string
	)
	switch mode1 {
	case "load-output":
		modeConfig = "output"
	case "load-input":
		modeConfig = "input"
	}
	return fmt.Sprintf("%v%v%v", s1, modeConfig, s2)
}

func countMode() string {
	return `
	/*
		Set table concurrency number
	*/
	mtc = 5
	count{}`
}
func rowsMode() string {
	var s1 = `rows {
		/*
			Set the verification mode. optional value [crc32,md5,data]
			[CRC32] crc32 uses the crc function to generate a digital checksum and compares data differences based on the checksum.
			[MD5] md5 uses the md5 function to generate a digital checksum and compares data differences based on the checksum.
			[DATA] Directly compare the original target row data characters.
			[COUNT] 
			If crc32 or md5 mode is used and the oracle database is involved, the corresponding function needs to be created in oracle, but not for MySQL
		*/
		options = data
		/*
			Set the delay verification time in seconds, 0 means closed.
		*/
		delay= 0
		/*
			Set data character comparison rules, mostly used in heterogeneous scenarios
		*/
		rule {
			/*
				Ignore multiple trailing zero differences due to data types
			*/
			end0:true
		}
		/*
			Set the execution timeout of the program sql statement. If it is 0, it means not to open it.
		*/
		endTime = 0
    	/*
			Whether the session connection retains the current sql_mode when querying and writing the original target database, optional value[true,false]
			[false] ---> set sql_mode=''
		*/
		sqlMode=false
		/*
			Whether to enable task resumption at breakpoints. After enabling it, successfully migrated tables will be automatically skipped.
			[true] -----> Skip the contents of the file result/breakPointFinish.point	
		*/
		breakpoint=false
		/*
			Set the consistency snapshot point. If it is 0, it means not to enable it. To enable it, it must be greater than 0.
			Limitation: 
				1) scn can only query the source side of the scope.
				2) Currently, scn is only effective for Oracle, so stay tuned for MySQL.
			get scn --> SELECT CURRENT_SCN FROM V$DATABASE;
		*/
		scn=0
	}`
	return s1
}
func currySetting(mode string) string {
	var (
		s = `
	/*
		Set table concurrency number
	*/
	mtc = 5
	`
		existMap = map[string]int{
			"sync":        1,
			"struct":      1,
			"object":      1,
			"load-output": 1,
			"load-input":  1,
			"rows":        1,
			"count":       1,
		}
	)
	if _, ok := existMap[mode]; ok {
		return s
	}
	return ""
}
func checkMod(c string) string {
	var modeConfig string
	var s = map[string]reflect.Value{
		"sync":        reflect.ValueOf(syncMode),
		"rows":        reflect.ValueOf(rowsMode),
		"struct":      reflect.ValueOf(structMode),
		"object":      reflect.ValueOf(objectMode),
		"count":       reflect.ValueOf(countMode),
		"load-output": reflect.ValueOf(loadMode),
		"load-input":  reflect.ValueOf(loadMode),
	}
	if v, ok := s[c]; ok {
		if v.Kind() == reflect.Func {
			f := v.Call(nil)
			for i := 0; i < len(f); i++ {
				modeConfig = fmt.Sprintf("%v", f[i].Interface())
			}
		}
	}
	return modeConfig
}
func rulesConfig(c string) string {
	var (
		s1 = `rules {	`
		s3 = `
}
		`
		buff bytes.Buffer
	)
	if c == "task" {
		return taskRulesExample()
	}
	buff.WriteString(s1)
	buff.WriteString(currySetting(c))
	buff.WriteString(checkMod(c))
	buff.WriteString(s3)
	s := buff.String()
	buff.Reset()
	return s
}

func repairConfig(f string) string {
	var (
		s = `repair {
`
		s1 = `
	/*
		Set the differential data repair method, optional value [table,file,no]
	*/
	fix = `
		s11 = `
	/*
		Set the differential data repair method, optional value [table]
	*/
	fix = `
		s2 = `
	/*
		Set the file name of the repair statement. It takes effect when fix = file. The default value is db2s-DataFix.sql.
	*/
	fileName = db2s-DataFix.sql
`
		s3 = `
	/*
		Check whether the repair statement is split according to the table. Optional values [split,many]
		[split] will generate a file according to each indication and store the repair statement of the table in the file.
		[many] means the repair statements for all tables are in one file	
	*/
	 way     = split`
		s4 = `
}`
		fixType = map[string]string{
			"rows":        "file",
			"sync":        "table",
			"load-output": "file",
			"load-input":  "table",
			"struct":      "file",
			"object":      "file",
		}
	)
	if v, ok := fixType[f]; ok {
		if strings.EqualFold(v, "table") {
			return fmt.Sprintf("%v%v%v%v", s, s11, v, s4)
		}
		return fmt.Sprintf("%v%v%v%v%v%v", s, s1, v, s2, s3, s4)
	}
	return ""
}
func resultConfig() string {
	s := `
resultOut {
	/*
		Set the terminal display mode, bar is the task progress bar. Optional value:[bar]
	*/
	tty = bar
	/*
		Whether to generate a result set. Optional value:[true,false]
	*/
	generalResult = true
	/*
		Set the format of the result set. Optional value:[xls]
	*/
	method = xls
	/*
		Set xls related option configuration
	*/
	xls {
		/*
			Set the file name of xls. default value:[gt_checksum_Result_Timestamp]
		*/
		fileName      = "gt_checksum_Result"
		/*
			Whether the xls sheet page is encrypted to ensure its security.Optional value:[true,false]
		*/
		safetySwitch   = true
	}
}`
	return s
}
func logConfig() string {
	var s = `
logs {
	/*
		Set the log file name of db2s
	*/
	file = "db2s.log"
	/*
		Set the log output level of db2s
	*/
	level = info
}
`
	return s
}
func getWriteFilePoint(objectFile any, mode string) (res any) {
	switch objectFile.(type) {
	case map[string]outPut.FileOperations:
		if v, ok := objectFile.(map[string]outPut.FileOperations)[mode]; ok {
			res = v
		}
	case outPut.FileOperations:
		return objectFile
	case map[string]*os.File:
		if v, ok := objectFile.(map[string]*os.File)[mode]; ok {
			res = v
		}
	}
	return
}
func writeConfig(objectFile any, mode, s string) (err error) {
	var gcConfig any
	if gcConfig = getWriteFilePoint(objectFile, mode); gcConfig == nil {
		return
	}
	switch gcConfig.(type) {
	case outPut.FileOperations:
		if err = gcConfig.(outPut.FileOperations).Write("", fmt.Sprintf("%v\n", s)); err != nil {
			return err
		}
	case *os.File:
		if _, err = gcConfig.(*os.File).WriteString(fmt.Sprintf("%v\n", s)); err != nil {
			return err
		}
	}
	return nil
}
func writeClose(objectFile any, mode string) (err error) {
	var gcConfig any
	if gcConfig = getWriteFilePoint(objectFile, mode); gcConfig == nil {
		return
	}
	switch gcConfig.(type) {
	case outPut.FileOperations:
		if err = gcConfig.(outPut.FileOperations).Close(); err != nil {
			return
		}
	case *os.File:
		if err = gcConfig.(*os.File).Close(); err != nil {
			return
		}
	}
	return
}

func WriteConfigFile(subConfigFileName []outPut.FileOperations, objectFile any, dSns DSNsS, mode string) error {
	if err := writeConfig(objectFile, mode, HeadConfig(dSns, mode)); err != nil {
		fmt.Println("-----err1:", err)
		return err
	}
	if err := writeConfig(objectFile, mode, schemaConfig(subConfigFileName, objectFile, mode)); err != nil {
		fmt.Println("-----err2:", err)
		return err
	}
	if err := writeConfig(objectFile, mode, rulesConfig(mode)); err != nil {
		fmt.Println("------err3:", err)
		return err
	}
	if err := writeConfig(objectFile, mode, logConfig()); err != nil {
		fmt.Println("-----err4:", err)
		return err
	}
	if err := writeConfig(objectFile, mode, repairConfig(mode)); err != nil {
		fmt.Println("-----err5:", err)
		return err
	}
	if err := writeConfig(objectFile, mode, resultConfig()); err != nil {
		fmt.Println("----err6:", err)
		return err
	}
	if err := writeClose(objectFile, mode); err != nil {
		fmt.Println("----err7:", err)
		return err
	}
	return nil
}

func InitConfigFile(objectFile any, mode string) error {

	if err := writeConfig(objectFile, mode, dSnsExample()); err != nil {
		return err
	}
	if err := writeConfig(objectFile, mode, schemaExample(mode)); err != nil {
		return err
	}
	if err := writeConfig(objectFile, mode, rulesExample(mode)); err != nil {
		return err
	}
	if err := writeConfig(objectFile, mode, logConfig()); err != nil {
		return err
	}
	if err := writeConfig(objectFile, mode, repairConfig(mode)); err != nil {
		return err
	}
	if err := writeConfig(objectFile, mode, resultConfig()); err != nil {
		return err
	}
	if err := writeClose(objectFile, mode); err != nil {
		return err
	}
	return nil
}
